﻿namespace MovieFactory.Data.Import.OBJLoader {
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using MovieFactory.Core.Collections;
    using MovieFactory.Entity.Scene;

    /// <summary>
    /// Basic OBJ file loader - assumes global indexing
    /// </summary>
    public class GlobalIndexObjLoader : IGeometryLoader {
        #region IGeometryLoader Members

        public SceneGeometryInfo Load( string fileName, params string[] additionalFiles ) {
            var result = new SceneGeometryInfo();
            var objs = new List<GeometryInfo>();
            string lastCommand = "f";

            FloatStore vb = null;
            FloatStore vt = null;
            FloatStore vn = null;

            IntStore ib = null;

            IntStore tib = null;
            IntStore nib = null;
            int oCount = 0;
            var buffer = new Queue<string>();
            using( var reader = new StreamReader(fileName) ) {
                while( !reader.EndOfStream ) {
                    var s = reader.ReadLine();
                    if( String.IsNullOrEmpty(s) )
                        continue;
                    buffer.Enqueue(s);
                }
            }

            while( buffer.Count > 0 ) {
                string line = buffer.Dequeue();

                if( string.IsNullOrEmpty(line) )
                    continue;
                if( line.StartsWith("#") )
                    continue;


                //try
                //{
                var tag = line.Contains(" ") ? line.Substring(0, line.IndexOf(" ")).Trim() : line.Trim();
                switch( tag ) {
                    case "vt":
                        if( vt == null ) {
                            vt = objs[oCount - 1].TextureData;
                        }
                        objs[oCount - 1].UseTextures = true;
                        vt.AddRange(ObjHandler.Process_VT(line));
                        break;
                    case "vn":

                        if( vn == null ) {
                            vn = objs[oCount - 1].NormalData;
                        }
                        objs[oCount - 1].UseNormals = true;
                        vn.AddRange(ObjHandler.Process_VN(line));
                        break;
                    case "v":
                        if( lastCommand.Equals("f") ) {
                            var newObj = new GeometryInfo();
                            objs.Add(newObj);
                            //vb = newObj.VertexBuffer;
                            vn = null;
                            ib = null;
                            tib = null;
                            nib = null;
                            oCount++;
                        }
                        if( vb == null ) {
                            vb = objs[oCount - 1].VertexData;
                        }
                        vb.AddRange(ObjHandler.Process_V(line));
                        break;

                    case "f":
                        if( ib == null ) {
                            ib = objs[oCount - 1].IndexData;
                        }
                        int[] tibs, nibs;
                        var ibValues = ObjHandler.Process_F(line, out nibs, out tibs);
                        bool TexInd = false;
                        bool NormInd = false;
                        ib.AddRange(ibValues);
                        if( objs[oCount - 1].UseTextures && objs[oCount - 1].UseNormals ) {
                            NormInd = true;
                            TexInd = true;
                        }
                        else if( objs[oCount - 1].UseTextures && !objs[oCount - 1].UseNormals ) {
                            TexInd = true;
                            tibs = nibs;
                        }
                        else
                            if( !objs[oCount - 1].UseTextures && objs[oCount - 1].UseNormals ) {
                                NormInd = true;
                            }
                        if( TexInd ) {
                            if( tib == null ) {
                                objs[oCount - 1].TextureIndexData = new IntStore();
                                tib = objs[oCount - 1].TextureIndexData;
                            }
                            if( tibs != null ) {
                                tib.AddRange(tibs);
                            }
                            else {
                                tib.AddRange(nibs);
                            }
                        }

                        if( NormInd ) {
                            if( nib == null ) {
                                objs[oCount - 1].NormalIndexData = new IntStore();
                                nib = objs[oCount - 1].NormalIndexData;
                            }
                            nib.AddRange(nibs);
                        }
                        break;

                    case "g":
                        if( lastCommand.Equals("f") ) {
                            var newObj = new GeometryInfo();
                            objs.Add(newObj);
                            //vb = null;
                            vn = null;
                            ib = null;
                            tib = null;
                            nib = null;
                            oCount++;
                        }
                        string name = string.Empty;
                        ObjHandler.Process_G(line, out name);
                        objs[oCount - 1].Name = name;
                        break;
                    case "usemtl":
                        string mtl = string.Empty;
                        ObjHandler.Process_Mtl(line, out mtl);
                        objs[oCount - 1].MaterialName = mtl.ToLowerInvariant();
                        break;
                    default:
                        continue;


                }
                lastCommand = tag;
                //}
                //catch (Exception ex)
                //{
                //    throw ex;
                //    //continue;

                //}
            }


            result.Vertices = vb.TodVectorList().ToArray();
            result.TexCoords = vt.TodVectorList().ToArray();
            result.Normals = vn.TodVectorList().ToArray();


            objs.ForEach(ob => {
                ob.IndexData.Iterator(Math.Abs);
                if( ob.TextureIndexData != null )
                    ob.TextureIndexData.Iterator(Math.Abs);
                if( ob.NormalIndexData != null )
                    ob.NormalIndexData.Iterator(Math.Abs);

                ob.Name = String.IsNullOrEmpty(ob.Name) ? Guid.NewGuid().ToString() : ob.Name;
            });


            return result;
        }

        #endregion

        #region  ObjParser

        public static class ObjHandler {
            public static float[] Process_V( string line ) {
                string[] vData = line.Replace("v ", string.Empty).Trim().Split(' ');

                float x = float.Parse(vData[0], NumberFormatInfo.InvariantInfo);
                float y = float.Parse(vData[1], NumberFormatInfo.InvariantInfo);
                float z = float.Parse(vData[2], NumberFormatInfo.InvariantInfo);
                return new[] { x, y, z };
            }


            public static float[] Process_VN( string line ) {
                string[] vData = line.Replace("vn ", string.Empty).Trim().Split(' ');

                float x = float.Parse(vData[0], NumberFormatInfo.InvariantInfo);
                float y = float.Parse(vData[1], NumberFormatInfo.InvariantInfo);
                float z = float.Parse(vData[2], NumberFormatInfo.InvariantInfo);
                return new[] { -x, -y, -z };
            }

            public static float[] Process_VT( string line ) {
                string[] vData = line.Replace("vt ", string.Empty).Trim().Split(' ');

                float x = float.Parse(vData[0], NumberFormatInfo.InvariantInfo);
                float y = float.Parse(vData[1], NumberFormatInfo.InvariantInfo);
                float z = 0f;
                if( vData.Length > 2 )
                    z = float.Parse(vData[2], NumberFormatInfo.InvariantInfo);
                return new[] { x, y, z };
            }

            public static int[] Process_F( string line, out int[] ins, out int[] its ) {
                var ib = new List<int>();
                ins = null;
                its = null;
                if( line.Contains('/') ) {
                    string[] data =
                        line.Trim().Remove(0, 2).Replace("/", ",").Split(',', ' ').Where(p => !String.IsNullOrEmpty(p)).Select(n => n.Trim(',')).ToArray();


                    if( data.Length == 9 ) {
                        ins = new int[3];
                        its = new int[3];

                        its[0] = (int.Parse(data[1]));
                        its[1] = (int.Parse(data[4]));
                        its[2] = (int.Parse(data[7]));

                        ins[0] = (int.Parse(data[2]));
                        ins[1] = (int.Parse(data[5]));
                        ins[2] = (int.Parse(data[8]));

                        ib.Add(int.Parse(data[0]));
                        ib.Add(int.Parse(data[3]));
                        ib.Add(int.Parse(data[6]));
                    }
                    else if( data.Length == 6 ) {
                        ins = new int[3];

                        ins[0] = (int.Parse(data[1]));
                        ins[1] = (int.Parse(data[3]));
                        ins[2] = (int.Parse(data[5]));

                        ib.Add(int.Parse(data[0]));
                        ib.Add(int.Parse(data[2]));
                        ib.Add(int.Parse(data[4]));
                    }
                    return ib.ToArray();
                }
                string[] vData = line.Replace('/', ' ').Replace("-", string.Empty).Trim().Split(' ');
                for( int i = 1; i < vData.Length; i++ ) {
                    ib.Add(int.Parse(vData[i].Trim(), CultureInfo.InvariantCulture));
                }
                return ib.ToArray();
            }

            public static void Process_Mtl( string line, out string mtl ) {
                mtl = line.Replace("usemtl ", string.Empty);
            }

            public static void Process_G( string line, out string name ) {
                name = line.Replace("g ", string.Empty);
            }
        }

        #endregion

    }
}
